import os
import sys
import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
from datetime import datetime, timedelta

# 添加项目根目录到系统路径
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))

from config.config import DATA_CONFIG, VISUALIZATION_CONFIG, MODEL_CONFIG, WEB_CONFIG
from src.data_collection.collector import WeatherDataCollector
from src.data_processing.processor import WeatherDataProcessor
from src.visualization.visualizer import WeatherVisualizer
from src.models.predictor import WeatherPredictor

# 初始化应用
app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.BOOTSTRAP],
    title=WEB_CONFIG['title']
)

# 初始化各模块
collector = WeatherDataCollector()
processor = WeatherDataProcessor()
visualizer = WeatherVisualizer()
predictor = WeatherPredictor()

# 获取城市列表
cities = [city['name'] for city in DATA_CONFIG['default_cities']]

# 定义应用布局
app.layout = dbc.Container([
    dbc.Row([
        dbc.Col([
            html.H1(WEB_CONFIG['title'], className="text-center my-4", style={'color': WEB_CONFIG['theme']['primary']}),
            html.Hr()
        ])
    ]),
    
    # 控制面板
    dbc.Row([
        dbc.Col([
            dbc.Card([
                dbc.CardHeader("控制面板"),
                dbc.CardBody([
                    dbc.Row([
                        dbc.Col([
                            html.Label("选择城市"),
                            dcc.Dropdown(
                                id="city-dropdown",
                                options=[{"label": city, "value": city} for city in cities],
                                value=cities[0],
                                clearable=False
                            )
                        ], width=4),
                        dbc.Col([
                            html.Label("数据类型"),
                            dcc.Dropdown(
                                id="data-type-dropdown",
                                options=[
                                    {"label": "当前天气", "value": "current"},
                                    {"label": "天气预报", "value": "forecast"},
                                    {"label": "历史天气", "value": "historical"},
                                    {"label": "所有数据", "value": "all"}
                                ],
                                value="forecast",
                                clearable=False
                            )
                        ], width=4),
                        dbc.Col([
                            html.Label("操作"),
                            html.Div([
                                dbc.Button("获取数据", id="fetch-data-button", color="primary", className="me-2"),
                                dbc.Button("处理数据", id="process-data-button", color="success", className="me-2"),
                                dbc.Button("训练模型", id="train-model-button", color="warning")
                            ])
                        ], width=4)
                    ])
                ])
            ], className="mb-4")
        ])
    ]),
    
    # 状态信息
    dbc.Row([
        dbc.Col([
            dbc.Alert(id="status-alert", is_open=False, duration=4000, color="info")
        ])
    ], className="mb-4"),
    
    # 主要内容区域 - 使用选项卡
    dbc.Row([
        dbc.Col([
            dbc.Tabs([
                # 数据可视化选项卡
                dbc.Tab([
                    dbc.Row([
                        dbc.Col([
                            html.H4("温度趋势", className="mt-3"),
                            dcc.Graph(id="temperature-graph")
                        ], width=12)
                    ]),
                    dbc.Row([
                        dbc.Col([
                            html.H4("湿度与气压", className="mt-3"),
                            dcc.Graph(id="humidity-pressure-graph")
                        ], width=6),
                        dbc.Col([
                            html.H4("天气状况分布", className="mt-3"),
                            dcc.Graph(id="weather-condition-graph")
                        ], width=6)
                    ])
                ], label="数据可视化", tab_id="visualization-tab"),
                
                # 预测分析选项卡
                dbc.Tab([
                    dbc.Row([
                        dbc.Col([
                            html.H4("温度预测", className="mt-3"),
                            dcc.Graph(id="temperature-prediction-graph")
                        ], width=12)
                    ]),
                    dbc.Row([
                        dbc.Col([
                            html.H4("模型评估", className="mt-3"),
                            html.Div(id="model-evaluation-results")
                        ], width=6),
                        dbc.Col([
                            html.H4("特征重要性", className="mt-3"),
                            dcc.Graph(id="feature-importance-graph")
                        ], width=6)
                    ])
                ], label="预测分析", tab_id="prediction-tab"),
                
                # 数据表格选项卡
                dbc.Tab([
                    dbc.Row([
                        dbc.Col([
                            html.H4("数据表格", className="mt-3"),
                            html.Div(id="data-table-container")
                        ], width=12)
                    ])
                ], label="数据表格", tab_id="data-table-tab")
            ], id="tabs", active_tab="visualization-tab")
        ])
    ])
], fluid=True)

# 回调函数 - 获取数据
@app.callback(
    Output("status-alert", "children"),
    Output("status-alert", "is_open"),
    Output("status-alert", "color"),
    Input("fetch-data-button", "n_clicks"),
    State("data-type-dropdown", "value"),
    prevent_initial_call=True
)
def fetch_data(n_clicks, data_type):
    if n_clicks is None:
        return "", False, "info"
    
    try:
        if data_type == "current":
            collector.get_current_weather()
            return "当前天气数据获取成功！", True, "success"
        elif data_type == "forecast":
            collector.get_weather_forecast()
            return "天气预报数据获取成功！", True, "success"
        elif data_type == "historical":
            # 这里可以添加获取历史数据的逻辑
            return "历史天气数据获取功能尚未实现", True, "warning"
        else:
            return "请选择有效的数据类型", True, "warning"
    except Exception as e:
        return f"获取数据失败: {str(e)}", True, "danger"

# 回调函数 - 处理数据
@app.callback(
    Output("status-alert", "children", allow_duplicate=True),
    Output("status-alert", "is_open", allow_duplicate=True),
    Output("status-alert", "color", allow_duplicate=True),
    Input("process-data-button", "n_clicks"),
    State("data-type-dropdown", "value"),
    prevent_initial_call=True
)
def process_data(n_clicks, data_type):
    if n_clicks is None:
        return "", False, "info"
    
    try:
        if data_type in ["current", "forecast", "historical"]:
            processor.process_data(data_type)
            return f"{data_type} 数据处理成功！", True, "success"
        elif data_type == "all":
            processor.merge_all_data()
            return "所有数据合并处理成功！", True, "success"
        else:
            return "请选择有效的数据类型", True, "warning"
    except Exception as e:
        return f"处理数据失败: {str(e)}", True, "danger"

# 回调函数 - 训练模型
@app.callback(
    Output("status-alert", "children", allow_duplicate=True),
    Output("status-alert", "is_open", allow_duplicate=True),
    Output("status-alert", "color", allow_duplicate=True),
    Input("train-model-button", "n_clicks"),
    State("city-dropdown", "value"),
    prevent_initial_call=True
)
def train_model(n_clicks, city):
    if n_clicks is None:
        return "", False, "info"
    
    try:
        # 加载数据
        df = processor.load_raw_data("historical")
        if df is None:
            return "没有可用的历史数据，请先获取数据", True, "warning"
        
        # 处理数据
        df_clean = processor.clean_data(df)
        df_transformed = processor.transform_data(df_clean)
        
        # 准备特征
        X, y, feature_cols = predictor.prepare_features(df_transformed, city=city)
        if X is None:
            return "准备特征失败，请检查数据", True, "warning"
        
        # 训练模型
        results = predictor.train_models(X, y, feature_cols, city=city)
        if results:
            return f"模型训练成功！最佳模型: {predictor.best_model_name}", True, "success"
        else:
            return "模型训练失败", True, "danger"
    except Exception as e:
        return f"训练模型失败: {str(e)}", True, "danger"

# 回调函数 - 更新温度趋势图
@app.callback(
    Output("temperature-graph", "figure"),
    Input("tabs", "active_tab"),
    Input("city-dropdown", "value"),
    Input("data-type-dropdown", "value"),
    prevent_initial_call=True
)
def update_temperature_graph(active_tab, city, data_type):
    if active_tab != "visualization-tab":
        return go.Figure()
    
    try:
        # 加载数据
        df = visualizer.load_data(data_type)
        if df is None or df.empty:
            return go.Figure().update_layout(title="没有可用数据")
        
        # 筛选城市
        if city and 'city' in df.columns:
            df = df[df['city'] == city]
            if df.empty:
                return go.Figure().update_layout(title=f"没有 {city} 的数据")
        
        # 确定时间列
        time_col = 'forecast_time' if 'forecast_time' in df.columns else 'timestamp'
        if time_col not in df.columns:
            return go.Figure().update_layout(title="数据中缺少时间列")
        
        # 创建图表
        fig = go.Figure()
        
        if 'temp' in df.columns:
            fig.add_trace(go.Scatter(
                x=df[time_col],
                y=df['temp'],
                mode='lines+markers',
                name='温度',
                line=dict(color=WEB_CONFIG['theme']['primary'], width=2)
            ))
        
        if 'temp_min' in df.columns and 'temp_max' in df.columns:
            fig.add_trace(go.Scatter(
                x=df[time_col],
                y=df['temp_min'],
                mode='lines',
                name='最低温度',
                line=dict(color=WEB_CONFIG['theme']['info'], width=1.5, dash='dot')
            ))
            
            fig.add_trace(go.Scatter(
                x=df[time_col],
                y=df['temp_max'],
                mode='lines',
                name='最高温度',
                line=dict(color=WEB_CONFIG['theme']['danger'], width=1.5, dash='dot')
            ))
        
        # 更新布局
        fig.update_layout(
            title=f"{city or '所有城市'} 温度趋势",
            xaxis_title="时间",
            yaxis_title="温度 (°C)",
            template=VISUALIZATION_CONFIG['plotly']['template'],
            height=VISUALIZATION_CONFIG['plotly']['height'],
            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
        )
        
        return fig
    except Exception as e:
        print(f"更新温度趋势图错误: {e}")
        return go.Figure().update_layout(title=f"加载数据错误: {str(e)}")

# 回调函数 - 更新湿度与气压图
@app.callback(
    Output("humidity-pressure-graph", "figure"),
    Input("tabs", "active_tab"),
    Input("city-dropdown", "value"),
    Input("data-type-dropdown", "value"),
    prevent_initial_call=True
)
def update_humidity_pressure_graph(active_tab, city, data_type):
    if active_tab != "visualization-tab":
        return go.Figure()
    
    try:
        # 加载数据
        df = visualizer.load_data(data_type)
        if df is None or df.empty:
            return go.Figure().update_layout(title="没有可用数据")
        
        # 筛选城市
        if city and 'city' in df.columns:
            df = df[df['city'] == city]
            if df.empty:
                return go.Figure().update_layout(title=f"没有 {city} 的数据")
        
        # 确定时间列
        time_col = 'forecast_time' if 'forecast_time' in df.columns else 'timestamp'
        if time_col not in df.columns:
            return go.Figure().update_layout(title="数据中缺少时间列")
        
        # 创建图表
        fig = make_subplots(specs=[[{"secondary_y": True}]])
        
        if 'humidity' in df.columns:
            fig.add_trace(
                go.Scatter(
                    x=df[time_col],
                    y=df['humidity'],
                    mode='lines+markers',
                    name='湿度 (%)',
                    line=dict(color=WEB_CONFIG['theme']['info'], width=2)
                ),
                secondary_y=False
            )
        
        if 'pressure' in df.columns:
            fig.add_trace(
                go.Scatter(
                    x=df[time_col],
                    y=df['pressure'],
                    mode='lines+markers',
                    name='气压 (hPa)',
                    line=dict(color=WEB_CONFIG['theme']['warning'], width=2)
                ),
                secondary_y=True
            )
        
        # 更新布局
        fig.update_layout(
            title=f"{city or '所有城市'} 湿度与气压",
            template=VISUALIZATION_CONFIG['plotly']['template'],
            height=VISUALIZATION_CONFIG['plotly']['height'],
            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
        )
        
        fig.update_xaxes(title_text="时间")
        fig.update_yaxes(title_text="湿度 (%)", secondary_y=False)
        fig.update_yaxes(title_text="气压 (hPa)", secondary_y=True)
        
        return fig
    except Exception as e:
        print(f"更新湿度与气压图错误: {e}")
        return go.Figure().update_layout(title=f"加载数据错误: {str(e)}")

# 回调函数 - 更新天气状况分布图
@app.callback(
    Output("weather-condition-graph", "figure"),
    Input("tabs", "active_tab"),
    Input("city-dropdown", "value"),
    Input("data-type-dropdown", "value"),
    prevent_initial_call=True
)
def update_weather_condition_graph(active_tab, city, data_type):
    if active_tab != "visualization-tab":
        return go.Figure()
    
    try:
        # 加载数据
        df = visualizer.load_data(data_type)
        if df is None or df.empty:
            return go.Figure().update_layout(title="没有可用数据")
        
        # 筛选城市
        if city and 'city' in df.columns:
            df = df[df['city'] == city]
            if df.empty:
                return go.Figure().update_layout(title=f"没有 {city} 的数据")
        
        # 检查是否有天气状况列
        if 'weather_condition' not in df.columns:
            return go.Figure().update_layout(title="数据中缺少天气状况信息")
        
        # 统计天气状况分布
        weather_counts = df['weather_condition'].value_counts().reset_index()
        weather_counts.columns = ['天气状况', '次数']
        
        # 创建图表
        fig = px.pie(
            weather_counts,
            values='次数',
            names='天气状况',
            title=f"{city or '所有城市'} 天气状况分布",
            color_discrete_sequence=px.colors.qualitative.Set3
        )
        
        # 更新布局
        fig.update_layout(
            template=VISUALIZATION_CONFIG['plotly']['template'],
            height=VISUALIZATION_CONFIG['plotly']['height']
        )
        
        return fig
    except Exception as e:
        print(f"更新天气状况分布图错误: {e}")
        return go.Figure().update_layout(title=f"加载数据错误: {str(e)}")

# 回调函数 - 更新温度预测图
@app.callback(
    Output("temperature-prediction-graph", "figure"),
    Input("tabs", "active_tab"),
    Input("city-dropdown", "value"),
    prevent_initial_call=True
)
def update_temperature_prediction_graph(active_tab, city):
    if active_tab != "prediction-tab":
        return go.Figure()
    
    try:
        # 加载数据
        df = processor.load_raw_data("forecast")
        if df is None or df.empty:
            return go.Figure().update_layout(title="没有可用的预报数据")
        
        # 处理数据
        df_clean = processor.clean_data(df)
        df_transformed = processor.transform_data(df_clean)
        
        # 筛选城市
        if city and 'city' in df_transformed.columns:
            df_city = df_transformed[df_transformed['city'] == city]
            if df_city.empty:
                return go.Figure().update_layout(title=f"没有 {city} 的数据")
        else:
            df_city = df_transformed
        
        # 尝试加载模型
        model_loaded = predictor.load_model('gradient_boosting', city=city)
        if not model_loaded:
            return go.Figure().update_layout(title=f"没有找到 {city} 的预测模型，请先训练模型")
        
        # 进行预测
        prediction_df = predictor.predict(df_city, days=7)
        if prediction_df is None or prediction_df.empty:
            return go.Figure().update_layout(title="预测失败")
        
        # 创建图表
        fig = go.Figure()
        
        # 添加实际温度（如果有）
        time_col = 'forecast_time' if 'forecast_time' in df_city.columns else 'timestamp'
        if time_col in df_city.columns and 'temp' in df_city.columns:
            fig.add_trace(go.Scatter(
                x=df_city[time_col],
                y=df_city['temp'],
                mode='lines+markers',
                name='实际温度',
                line=dict(color=WEB_CONFIG['theme']['primary'], width=2)
            ))
        
        # 添加预测温度
        fig.add_trace(go.Scatter(
            x=prediction_df['date'],
            y=prediction_df['temp'],
            mode='lines+markers',
            name='预测温度',
            line=dict(color=WEB_CONFIG['theme']['danger'], width=2, dash='dash')
        ))
        
        # 更新布局
        fig.update_layout(
            title=f"{city} 未来7天温度预测",
            xaxis_title="日期",
            yaxis_title="温度 (°C)",
            template=VISUALIZATION_CONFIG['plotly']['template'],
            height=VISUALIZATION_CONFIG['plotly']['height'],
            legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
        )
        
        return fig
    except Exception as e:
        print(f"更新温度预测图错误: {e}")
        return go.Figure().update_layout(title=f"预测错误: {str(e)}")

# 回调函数 - 更新特征重要性图
@app.callback(
    Output("feature-importance-graph", "figure"),
    Input("tabs", "active_tab"),
    Input("city-dropdown", "value"),
    prevent_initial_call=True
)
def update_feature_importance_graph(active_tab, city):
    if active_tab != "prediction-tab":
        return go.Figure()
    
    try:
        # 尝试加载模型
        model_loaded = predictor.load_model('gradient_boosting', city=city)
        if not model_loaded:
            return go.Figure().update_layout(title=f"没有找到 {city} 的预测模型，请先训练模型")
        
        # 检查是否有特征重要性
        if predictor.feature_importance is None:
            return go.Figure().update_layout(title="没有特征重要性信息")
        
        # 创建特征重要性DataFrame
        importance_df = pd.DataFrame({
            '特征': list(predictor.feature_importance.keys()),
            '重要性': list(predictor.feature_importance.values())
        }).sort_values('重要性', ascending=False)
        
        # 创建图表
        fig = px.bar(
            importance_df,
            x='重要性',
            y='特征',
            orientation='h',
            title=f"{city} 温度预测特征重要性",
            color='重要性',
            color_continuous_scale='Viridis'
        )
        
        # 更新布局
        fig.update_layout(
            template=VISUALIZATION_CONFIG['plotly']['template'],
            height=VISUALIZATION_CONFIG['plotly']['height']
        )
        
        return fig
    except Exception as e:
        print(f"更新特征重要性图错误: {e}")
        return go.Figure().update_layout(title=f"加载错误: {str(e)}")

# 回调函数 - 更新模型评估结果
@app.callback(
    Output("model-evaluation-results", "children"),
    Input("tabs", "active_tab"),
    Input("city-dropdown", "value"),
    prevent_initial_call=True
)
def update_model_evaluation_results(active_tab, city):
    if active_tab != "prediction-tab":
        return html.Div()
    
    try:
        # 尝试加载模型
        model_loaded = predictor.load_model('gradient_boosting', city=city)
        if not model_loaded:
            return html.Div("没有找到预测模型，请先训练模型")
        
        # 加载数据
        df = processor.load_raw_data("historical")
        if df is None or df.empty:
            return html.Div("没有可用的历史数据")
        
        # 处理数据
        df_clean = processor.clean_data(df)
        df_transformed = processor.transform_data(df_clean)
        
        # 筛选城市
        if city and 'city' in df_transformed.columns:
            df_city = df_transformed[df_transformed['city'] == city]
            if df_city.empty:
                return html.Div(f"没有 {city} 的数据")
        else:
            df_city = df_transformed
        
        # 准备特征
        X, y, _ = predictor.prepare_features(df_city, city=city)
        if X is None or y is None:
            return html.Div("准备特征失败")
        
        # 划分测试集
        _, X_test, _, y_test = train_test_split(
            X, y, test_size=predictor.test_size, random_state=predictor.random_state
        )
        
        # 评估模型
        eval_results = predictor.evaluate_model(X_test, y_test)
        if eval_results is None:
            return html.Div("模型评估失败")
        
        # 创建评估结果表格
        table_header = [
            html.Thead(html.Tr([
                html.Th("指标"),
                html.Th("值")
            ]))
        ]
        
        rows = [
            html.Tr([html.Td("均方根误差 (RMSE)"), html.Td(f"{eval_results['rmse']:.4f}")]),
            html.Tr([html.Td("平均绝对误差 (MAE)"), html.Td(f"{eval_results['mae']:.4f}")]),
            html.Tr([html.Td("决定系数 (R²)"), html.Td(f"{eval_results['r2']:.4f}")]),
            html.Tr([html.Td("模型类型"), html.Td(predictor.best_model_name)])
        ]
        
        table_body = [html.Tbody(rows)]
        
        table = dbc.Table(table_header + table_body, bordered=True, hover=True, striped=True)
        
        return html.Div([
            table,
            html.P(f"模型评估基于 {len(y_test)} 个测试样本")
        ])
    except Exception as e:
        print(f"更新模型评估结果错误: {e}")
        return html.Div(f"加载评估结果错误: {str(e)}")

# 回调函数 - 更新数据表格
@app.callback(
    Output("data-table-container", "children"),
    Input("tabs", "active_tab"),
    Input("city-dropdown", "value"),
    Input("data-type-dropdown", "value"),
    prevent_initial_call=True
)
def update_data_table(active_tab, city, data_type):
    if active_tab != "data-table-tab":
        return html.Div()
    
    try:
        # 加载数据
        df = visualizer.load_data(data_type)
        if df is None or df.empty:
            return html.Div("没有可用数据")
        
        # 筛选城市
        if city and 'city' in df.columns:
            df = df[df['city'] == city]
            if df.empty:
                return html.Div(f"没有 {city} 的数据")
        # 创建数据表格
        table = dbc.Table(
            # 表头
            [html.Thead(html.Tr([html.Th(col) for col in df.columns]))] +
            # 表体
            [html.Tbody([
                html.Tr([
                    html.Td(df.iloc[i][col]) for col in df.columns
                ]) for i in range(min(len(df), 50))  # 限制最多显示50行
            ])],
            bordered=True,
            hover=True,
            responsive=True,
            striped=True
        )
        
        return html.Div([
            html.P(f"显示前50行数据（共{len(df)}行）"),
            table
        ])
    except Exception as e:
        print(f"更新数据表格错误: {e}")
        return html.Div(f"加载数据表格错误: {str(e)}")


# 添加应用启动入口
if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=8050)
